home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Freeware 1998 June
/
SGI Freeware 1998 June.iso
/
dist
/
fw_ATxgopher.idb
/
usr
/
freeware
/
src
/
xgopher.1.3
/
util.c.z
/
util.c
Wrap
C/C++ Source or Header
|
1998-01-21
|
24KB
|
1,131 lines
/* util.c
Utility routines to maintain and process gopher data structures
and events. */
/*---------------------------------------------------------------*/
/* Xgopher version 1.3 08 April 1993 */
/* version 1.2 20 November 1992 */
/* version 1.1 20 April 1992 */
/* version 1.0 04 March 1992 */
/* X window system client for the University of Minnesota */
/* Internet Gopher System. */
/* Allan Tuchman, University of Illinois at Urbana-Champaign */
/* Computing and Communications Services Office */
/* Copyright 1992, 1993 by */
/* the Board of Trustees of the University of Illinois */
/* Permission is granted to freely copy and redistribute this */
/* software with the copyright notice intact. */
/*---------------------------------------------------------------*/
/* ***** really, this file needs to divide into 3:
itemPublic.c itemUtil.c itemClass.c
there are some global variables to consider, though.
***** */
#include <stdio.h>
#include "osdep.h"
#include "conf.h"
#include "gopher.h"
#include "globals.h"
#include "gui.h"
#include "item.h"
#include "itemList.h"
#include "dir.h"
#include "dirList.h"
#include "util.h"
#include "status.h"
#include "misc.h"
#include "net.h"
#include "appres.h"
#include "jobs.h"
#include <ctype.h>
#include <errno.h>
#include "sc_text.h"
#include "sc_dir.h"
#include "sc_cso.h"
#include "sc_index.h"
#include "sc_telnet.h"
#include "sc_tn3270.h"
#include "sc_binary.h"
#include "sc_image.h"
#include "sc_sound.h"
#include "sc_extend.h"
#ifdef Mips
#ifdef BSD43
extern int errno;
#endif
#endif /* Mips */
char prefixUnknown [ PREFIX_LEN ];
scInfo noSubclass = {
"unknown gopher item type",
prefixUnknown,
&nullHostList,
GI_access,
GI_copyToFile,
copyTypeNone,
GI_process,
GI_init,
GI_done,
GI_restart
};
#define UNKNOWN_TYPE(gi) ((gi)->sc == &noSubclass)
typedef struct giSubclassStruct {
char typeID;
scInfo *subclass;
struct giSubclassStruct *next;
} giSubclass;
/* the order of the following subclasses is important. All directory
types must come first, then the extended types (so they can
override internal ones), then all other built-in types. */
static giSubclass definedSubclass[] = {
{A_DIRECTORY, &dirSubclass, NULL},
{A_INDEX, &indexSubclass, NULL},
{A_EXTENDED, &extendSubclass,NULL},
{A_FILE, &textSubclass, NULL},
{A_CSO, &csoSubclass, NULL},
{A_BINARY, &binarySubclass,NULL},
{A_IMAGE, &imageSubclass, NULL},
{A_TELNET, &telnetSubclass,NULL},
{A_TN3270, &tn3270Subclass,NULL},
{A_SOUND, &soundSubclass, NULL},
/* '?' type will be Unknown */
{'?', &noSubclass, NULL},
{'\0', &noSubclass, NULL},
};
/* the following can be implemented as extended types:
A_MAC_BINHEX, (mac binhex),
A_DOS_BINHEX, (dos binhex),
A_UNIX_UUENCODE, (uuencoded file)
*/
static giSubclass *activeSubclass = NULL;
/* ====================================================================== */
/* */
/* publicly callable procedures */
/* */
/* ====================================================================== */
/* makeItem
Given the contents, allocate and build a new gopher item */
gopherItemP
makeItem(t, titleString, selectString, host, port, plus)
char t;
char *titleString;
char *selectString;
char *host;
int port;
BOOLEAN plus;
{
gopherItemP gi = newItem();
giSubclass *scID;
gi->type = t;
strncpy(USER_STRING(gi), titleString, USER_STRING_LEN);
vStringSet(&(gi->selector), selectString);
strncpy(gi->host, host, HOST_STRING_LEN);
gi->port = port;
gi->plus = FALSE;
/* determine subclass and set fields determined by those class
variables and class methods. */
for (scID = activeSubclass;
scID->typeID != t && scID->typeID != A_UNKNOWN;
scID=scID->next) ;
gi->sc = scID->subclass;
PREFIX(gi, gi->sc->typePrefix);
gi->accessOk = gi->sc->checkAccess(gi);
return gi;
}
/* initItemClasses
initialize each item class - gather its resources, restrictions,
and if appropriate, create its user interface resources. */
void
initItemClasses()
{
giSubclass *scID;
for (scID = definedSubclass; scID->typeID != '\0'; scID++) {
scID->subclass->initProc();
}
#ifdef DEBUG
fprintf (stderr, "Known item types are:\n");
for (scID = activeSubclass; scID != NULL; scID=scID->next) {
fprintf (stderr, "\t%c, %.5s, %s\n",
scID->typeID, scID->subclass->typePrefix,
scID->subclass->typeName);
}
#endif /* DEBUG */
return;
}
/* doneItemClasses
terminate each item class - free its resources and user interface
resources as necessary. */
void
doneItemClasses()
{
giSubclass *scID;
for (scID = activeSubclass; scID != NULL; scID=scID->next) {
scID->subclass->doneProc();
}
killAllItemProcesses();
return;
}
/* restartItemClasses
restart each item class - re-initialize its resources and user
interface components as necessar. */
void
restartItemClasses()
{
giSubclass *scID;
for (scID = activeSubclass; scID != NULL; scID=scID->next) {
scID->subclass->restartProc();
}
return;
}
/* processItem
check accessability, then process the selected item */
BOOLEAN
processItem(gi)
gopherItemP gi;
{
BOOLEAN result=FALSE;
if (! gi->accessOk) {
char message[MESSAGE_STRING_LEN];
sprintf(message,
"Sorry, %s access is restricted in this session.",
gi->sc->typeName);
showError(message);
result = FALSE;
} else {
result = gi->sc->processItem(gi);;
}
return result;
}
/* copyItemToFile
check accessability, then copy the selected item to a file */
BOOLEAN
copyItemToFile(gi)
gopherItemP gi;
{
BOOLEAN result=FALSE;
if (! gi->accessOk) {
char message[MESSAGE_STRING_LEN];
sprintf(message,
"Sorry, %s access is restricted in this session.",
gi->sc->typeName);
showError(message);
result = FALSE;
} else {
result = gi->sc->copyProc(gi);;
}
return result;
}
/* updateDirectory
reload an existing gopher directory (possibly an index search result) */
void
updateDirectory(d)
gopherDirP d;
{
int s;
gopherItemP gi = d->selectorItem;
BOOLEAN fetchOK;
if ( (s = GI_connectWithStatus(gi)) < 0) return;
writeString(s, vStringValue(&(gi->selector)));
writeString(s, EOL_STRING);
showStatus("Reloading requested directory", STAT_DIRECTORY,
gi->host, gi->port);
fetchOK = GI_getGopherDir(s, d);
close(s);
if (! removeStatusPanel() ) {
/* someone cancelled as the directory load finished */
return;
}
if (! fetchOK) {
/* failure to re-load the directory */
showError(
"There seems to be no information in this directory\n");
return;
}
setDirTime(d);
return;
}
/* ====================================================================== */
/* */
/* Utilities used by the item class and its subclasses */
/* */
/* ====================================================================== */
/* GU_ftpCheck
return True (ok) if ftp is allowed or this item doesn't represent
an ftp request. Return false if this this is an ftp request and
ftp's are not allowed. */
static BOOLEAN
GU_ftpCheck(gi)
gopherItemP gi;
{
BOOLEAN isFtp = FALSE;
if ( appResources->allowFtp ) return TRUE;
isFtp = (strncmp(vStringValue(&(gi->selector)), "ftp:", 4) == 0);
/* don't see an "ftp:", but see if there's an '@' in the path */
if (! isFtp) {
isFtp = (index(vStringValue(&(gi->selector)), '@') != NULL);
}
return ( ! isFtp );
}
/* GU_makePrefix
edit application resources of directory listing prefixes to conform
to be blank padded and exactly PREFIX_LEN characters.
NOTE: These strings are *NOT* NULL-terminated. */
void
GU_makePrefix(target, source)
char *target, *source;
{
int i;
for (i=0; i<PREFIX_LEN; i++) {
if (*source == NULLC) {
*(target++) = ' ';
} else {
*(target++) = *(source++);
}
}
}
/* GU_getGopherItem
receive a response from a gopher server, parsing it as it comes in.
Return the item pointer for success, NULL for failure. */
static gopherItemP
GU_getGopherItem(gfd)
int gfd;
{
char buffer[255];
char gType,
gName[USER_STRING_LEN],
gPath[SELECTOR_STRING_MAX_LEN],
gHost[HOST_STRING_LEN];
int gPort;
BOOLEAN gPlus;
if (readn(gfd, &(buffer[0]), 1) <= 0) {
/* immediate EOF or error -- no item read */
return NULL;
}
/** Get the kind of file from the first character **/
gType = buffer[0];
if (gType == A_EOI) return NULL;
if (readField(gfd, &(buffer[1]), 255) <= 0) {
/* immediate EOF or error -- no item read */
return NULL;
}
/* get the User Displayable name */
strncpy(gName, buffer + 1, USER_STRING_LEN);
/* terminate the string just in case it was too long */
gName[USER_STRING_LEN - 1] = '\0';
/* get the Pathname */
if (readField(gfd, gPath, SELECTOR_STRING_MAX_LEN) <= 0) {
/* EOF or error after TYPE/NAME --
no complete item read */
return NULL;
}
/* get the hostname */
if (readField(gfd, gHost, HOST_STRING_LEN) == 0) {
/* EOF or error after TYPE/NAME/PATH --
no complete item read */
return NULL;
}
/* get the port number */
if (readLine(gfd, buffer, 255)<=0) {
/* EOF or error after TYPE/NAME/PATH --
Problem - should be a port number here. But this
isn't as serious as above. We'll try to proceed. */
;
}
gPort = 0;
/* Get the port number */
gPort = atoi(buffer);
gPlus = False;
return makeItem(gType, gName, gPath, gHost, gPort, gPlus);
}
/* GU_copySubclassRecord
return a copy of the class record for subclass identified.
Search both knownTypes, and types already initialized. */
scInfo *
GU_copySubclassRecord(t)
char t;
{
scInfo *scRec;
giSubclass *scID;
scRec = (scInfo *) malloc(sizeof(scInfo));
/* first check initialized types. This allows an alias
to preferentially use a new external type. */
for (scID = activeSubclass;
scID != NULL && scID->typeID != t; scID=scID->next) ;
if (scID == NULL) {
/* it will be in this list or the final item type, unknown
will be used */
for (scID = definedSubclass;
scID->typeID != t && scID->typeID != '\0'; scID++) ;
}
bcopy((char *) scID->subclass, (char *) scRec, sizeof(scInfo));
return scRec;
}
/* GU_registerNewType
Add a new type identifier and its subclass record to the known list. */
void
GU_registerNewType(t, scRec)
char t;
scInfo *scRec;
{
giSubclass *newSC = (giSubclass *) malloc (sizeof (giSubclass));
giSubclass *sc;
newSC->typeID = t;
newSC->subclass = scRec;
newSC->next = (giSubclass *) NULL;
/* add to the end of the list for expected behavior */
if (activeSubclass == (giSubclass *) NULL) {
activeSubclass = newSC;
} else {
for (sc=activeSubclass; sc != NULL; sc=sc->next) {
if (sc->typeID == t) { /* already is one */
break;
}
if (sc->next == (giSubclass *) NULL) {
sc->next = newSC;
break;
}
}
}
}
/* GU_isVowel
return True if first character of a string is a vowel, false otherwise */
static char vowels[] = "aAeEiIoOuU";
BOOLEAN
GU_isVowel(str)
char *str;
{
char *v;
for (v=vowels; (*str != *v && *v != NULLC); v++ );
return (*v != NULLC);
}
/* Ideas for the implementation of the access limiting mechanism
came from the University of Minnesota gopher server code.
In particular, the matching of IP addresses and host name
matching algorithm is from that code. */
/* GU_addHostToAccessList
add the host name or address to an access list.
Determine if a given string is a name or IP address by
checking the first character to see if it is numeric. */
static void
GU_addHostToAccessList(list, host)
accessList *list;
char *host;
{
char *temp;
accessList addr;
temp = (char *) malloc((strlen(host) + 1) * sizeof(char));
strcpy(temp, host);
addr = (accessListItem *) malloc(sizeof(accessListItem));
addr->address = temp;
addr->numeric = isdigit(*host) ? TRUE : FALSE;
/* For matching, it should not matter whether the list is
created in the same order as specified or reverse order,
so each item in the input list will be added to the front. */
addr->next = *list;
*list = addr;
return;
}
/* GU_createAccessList
create an access list from a string of hostname/IP addresses */
accessList
GU_createAccessList(string)
char *string;
{
/* pull out host or domain names separated by white space */
char *cp;
char name[256];
char *np;
accessList list = (accessList) NULL;
cp = string;
while (*cp != '\0') {
while (isspace(*cp) && *cp != '\0') { cp++; };
if ( *cp == '\0' ) break;
np = &(name[0]);
while (! isspace(*cp) && *cp != '\0') {
*np = *cp;
np++;
cp++;
}
*np='\0';
GU_addHostToAccessList(&list, name);
}
return list;
}
/* GU_checkAccess
see if a proposed host access is allowed */
BOOLEAN
GU_checkAccess(host, permitList)
char *host;
accessList permitList;
{
accessListItem *allowed;
int hostLen, addrLen;
if (permitList == NULL) return TRUE;
for (allowed=permitList; allowed!=NULL; allowed=allowed->next) {
hostLen = strlen(host);
addrLen = strlen(allowed->address);
if (allowed->numeric) {
/* numeric IP address -- compare from start */
if (addrLen <= hostLen) {
if (strncmp(host, allowed->address, addrLen) == 0) {
return TRUE;
}
}
} else {
/* character host/domain name -- compare from end */
if (addrLen <= hostLen) {
if (strcasecmp((host+hostLen-addrLen),
allowed->address) == 0) {
return TRUE;
}
}
}
}
return FALSE;
}
#ifdef DEBUG
void
GU_printAccessList(list)
accessList list;
{
accessListItem *l;
fprintf (stdout, "Host Access List:\n");
for (l=list; l != (accessList) NULL; l=l->next) {
fprintf (stdout, "\t%s %s\n", l->address,
l->numeric ? "(IP address)" : "(host name)");
}
fprintf (stdout, " End of Host Access List\n");
}
#endif /* DEBUG */
/* ====================================================================== */
/* */
/* Class methods and common subclass methods of gopher item */
/* */
/* ====================================================================== */
/* GI_connnectWithStatus
make a socket connection, posting the status popup and leaving it
posted for the following processing */
int
GI_connectWithStatus(gi)
gopherItemP gi;
{
int s;
showStatus("Connecting to Gopher", STAT_CONNECT,
gi->host, gi->port);
s = connectToSocket(gi->host, gi->port);
if (s < 0) {
(void) removeStatusPanel();
/* could not make a network connection to receive file */
networkError(s, gi->host, gi->port);
return -1;
}
/* check for cancel during connection */
if (!updateStatusPanel(1, 0)) {
(void) removeStatusPanel();
close(s);
return -1;
}
return s;
}
/* GI_getGopherDir
receive a directory from a gopher server.
Return the directory pointer for success, NULL for failure. */
BOOLEAN
GI_getGopherDir(gfd, d)
int gfd;
gopherDirP d;
{
gopherItemP gi;
char buffer[512];
int j, n=0;
BOOLEAN goOn;
/* get each item; for each one, decide if it should be kept
in the directory. */
while ( (gi = GU_getGopherItem(gfd)) != NULL ) {
n++;
if (appResources->showItems == showAll) {
appendItem(&(d->contents), gi);
}
else if (appResources->showItems == showKnown &&
! UNKNOWN_TYPE(gi)) {
appendItem(&(d->contents), gi);
}
else if (appResources->showItems == showAccessible &&
gi->accessOk) {
appendItem(&(d->contents), gi);
}
else if (appResources->showItems == showAvailable &&
! UNKNOWN_TYPE(gi) && gi->accessOk) {
appendItem(&(d->contents), gi);
}
else {
freeItem(gi);
n--;
}
if (n % 5 == 0) {
goOn = updateStatusPanel(n, 0);
if (!goOn) {
freeItemList(&(d->contents));
break;
}
}
}
return (itemListLength(&(d->contents)) != 0);
}
/* GI_copyFromNet
Generic copy setup. Makes network connection, updates status
and invokes a type-specific copy procedure. */
BOOLEAN
GI_copyFromNet(outFD, gi, specialMsg)
int outFD;
gopherItemP gi;
char *specialMsg;
{
int s;
char message[MESSAGE_STRING_LEN];
BOOLEAN rc, goOn = TRUE;
if (gi->sc->copyDataType == copyTypeNone) return;
if (specialMsg == (char *) NULL) {
sprintf(message, "Copying the %s", gi->sc->typeName);
} else {
strcpy(message, specialMsg);
}
if ((s = GI_connectWithStatus(gi)) < 0) return FALSE;
writeString(s, vStringValue(&(gi->selector)));
writeString(s, EOL_STRING);
switch (gi->sc->copyDataType) {
case copyTypeAscii:
rc = GI_copyAsciiFromNet(s, outFD, gi, message);
break;
case copyTypeBinaryEOF:
rc = GI_copyBinaryFromNetEOF(s, outFD, gi, message);
break;
default:
rc = FALSE;
break;
}
close(s);
/* check for cancel */
goOn = removeStatusPanel();
return (rc && goOn);
}
#define MIN_OF(a,b) ((a)<(b) ? (a) : (b))
#define BIN_BUFFER_SIZE 1400
/* GI_copyBinaryFromNetEOF
Binary copy the contents of one open file descriptor to another.
Copy until EOF
The file descriptor references on open file for the result.
For input, we will assume blocking I/O, so a read will always
return something if it's not yet eof. */
BOOLEAN
GI_copyBinaryFromNetEOF(s, outFD, gi, msg)
int s, outFD;
gopherItemP gi;
char *msg;
{
char buf[BIN_BUFFER_SIZE];
int nr, nw;
int byteCt=0;
BOOLEAN goOn = TRUE;
showStatus(msg, STAT_BINARY,
gi->host, gi->port);
while (TRUE) {
nr = read(s, buf, BIN_BUFFER_SIZE);
if (nr <= 0) {
if (nr == -1) {
perror("GI_copyBinaryFromNetEOF");
fprintf (stderr,
"Error copying binary data - input.\n");
}
break;
}
byteCt += nr;
goOn = updateStatusPanel(byteCt, 0);
if (!goOn) {
break;
}
nw = write(outFD, buf, nr);
if (nw != nr || nw < 0) {
perror("GI_copyBinaryFromNetEOF");
fprintf (stderr,
"Error copying binary data - output.\n");
break;
}
}
return goOn;
}
/* GI_copyBinaryFromNetLen
Binary copy the contents of one open file descriptor to another.
Copy until a specific number of characters has been copied.
The file descriptor references on open file for the result.
For input, we will assume blocking I/O, so a read will always
return something if it's not yet eof. */
BOOLEAN
GI_copyBinaryFromNetLen(s, outFD, gi, msg)
int s, outFD;
gopherItemP gi;
char *msg;
{
char buf[BIN_BUFFER_SIZE];
int nr, nw;
int byteCt=0;
BOOLEAN goOn = TRUE;
int nBytes = 0;
showStatus(msg, STAT_BINARY,
gi->host, gi->port);
while (nBytes > 0) {
nr = read(s, buf, MIN_OF(nBytes, BIN_BUFFER_SIZE));
if (nr <= 0) {
if (nr == -1) {
perror("GI_copyBinaryFromNetLen");
fprintf (stderr,
"Error copying binary data - input.\n");
}
break;
}
nBytes -= nr;
byteCt += nr;
goOn = updateStatusPanel(byteCt, 0);
if (!goOn) {
break;
}
nw = write(outFD, buf, nr);
if (nw != nr || nw < 0) {
perror("GI_copyBinaryFromNetLen");
fprintf (stderr,
"Error copying binary data - output.\n");
break;
}
}
return goOn;
}
/* GI_copyAsciiFromNet
Copy the contents of one open text file descriptor to another. Copy
until a line with a single period in column one or an end of file.
Change "network ASCII" with CR-LF ending each line to standard
Unix text files with a NL at the end of each line.
Either file descriptor may reference a file or a socket connection.
For sockets, we will assume blocking I/O, so a read will always
return something if it's not yet eof, and a write will write everything. */
static char newLine[1] = {NL};
BOOLEAN
GI_copyAsciiFromNet(s, outFD, gi, msg)
int s, outFD;
gopherItemP gi;
char *msg;
{
char line[FILE_LINE_LEN];
int len;
int lineCt=0, wordCt=0;
BOOLEAN goOn = TRUE;
showStatus(msg, STAT_ASCII,
gi->host, gi->port);
if (readLine(s, line, FILE_LINE_LEN) <= 0) {
(void) removeStatusPanel();
return FALSE;
}
zapCRLF(line);
while ( line[0] != '.' || line[1] != '\0') {
lineCt++;
if ( (lineCt < 50 && lineCt % 10 == 0) ||
(lineCt < 500 && lineCt % 50 == 0) ||
lineCt % 100 == 0 ) {
goOn = updateStatusPanel(lineCt, 0);
if (!goOn) {
break;
}
}
len = strlen(line);
write(outFD, line, len);
write(outFD, newLine, 1);
if (readLine(s, line, FILE_LINE_LEN) <= 0) break;
zapCRLF(line);
}
return goOn;
}
#define NET_BUFFER_SIZE 1400
/* GI_copyNetUntilEOF
copy chunks of data from a network file descriptor to a file pointer.
No termination condition is expected except EOF on the input fd.
This is a blocking read. */
void
GI_copyNetUntilEOF(s, fp)
int s;
FILE *fp;
{
char buf[NET_BUFFER_SIZE];
int j;
while (TRUE) {
j = read(s, buf, NET_BUFFER_SIZE);
if (j <= 0) {
if (j == -1)
fprintf (stderr,
"Error (%d) copying data from the network\n",
errno);
break;
}
if (fwrite(buf, 1, j, fp) != j) {
return;
}
}
}
/* GI_noCopyItem
default "can't copy" procedure */
BOOLEAN
GI_noCopyItem(gi)
gopherItemP gi;
{
int s;
char *lastName;
char message[MESSAGE_STRING_LEN];
sprintf(message, "You cannot copy %s %s to a file.",
(GU_isVowel(gi->sc->typeName) ? "an" : "a"), gi->sc->typeName);
showError(message);
return FALSE;
}
/* GI_copyToFile
copy a gopher item directly to a file */
BOOLEAN
GI_copyToFile(gi)
gopherItemP gi;
{
saveNetRequest(gi);
return TRUE;
}
/* GI_access
check the access limits for an item. This class method will be
overridden for many subclasses which need to check the application
resources for permission. */
BOOLEAN
GI_access(gi)
gopherItemP gi;
{
BOOLEAN result;
result = GU_ftpCheck(gi);
return result;
}
/* GI_init
default class initialization procedure a subclass of Gopher Item. This
procedure works for class "unknown". */
void
GI_init()
{
GU_makePrefix(prefixUnknown, appResources->prefixUnknown);
GU_registerNewType(A_UNKNOWN, &noSubclass);
return;
}
/* GI_done
default class termination procedure a subclass of Gopher Item. This
procedure is a no-op. */
void
GI_done()
{
return;
}
/* GI_restart
default restart cleanup for a subclass of Gopher Item. This
procedure is a no-op. */
void
GI_restart()
{
return;
}
/* GI_process
default processing procedure for a subclass of Gopher Item. This
procedure is a no-op, suggesting an alternative means of processing
the item. Every known item type should override this procedure. */
BOOLEAN
GI_process(gi)
gopherItemP gi;
{
char message[MESSAGE_STRING_LEN];
sprintf(message,
"There is no automatic processing for this gopher item type (%c)",
gi->type);
strcat(message,
"\nYou may be able to use the Copy command to access the file.");
showError(message);
return FALSE;
}